﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using JetBlack.Diagnostics;
using JetBlack.SimpleTopicBus.Messages;
using JetBlack.Text;

namespace JetBlack.SimpleTopicBus.Distributor
{
    internal class NotificationManager
    {
        private class Notification
        {
            internal readonly List<Interactor> Interactors = new List<Interactor>();
            internal readonly Regex Pattern;

            internal Notification(Regex pattern)
            {
                Pattern = pattern;
            }
        }

        /// <summary>
        /// This event gets called when a new notification request is made.
        /// This allows existing subscriptions to be checked. Where the
        /// topics match the subscriptions are forwarded to the
        /// notifiable interactor.
        /// </summary>
        internal event Action<Regex, Interactor> OnNewNotification;

        private readonly Dictionary<string, Notification> cache = new Dictionary<string, Notification>();

        /// <summary>
        /// This method removes all associations between notifications and this
        /// interactor. This is required when a client disconnects.
        /// </summary>
        /// <param name="notifiable">The notifiable interactor</param>
        internal void RemoveInteractor(Interactor notifiable)
        {
            AppEnvironment.TraceSource.Verbose("Removing notification requests from {0}", notifiable);

            List<string> emptyNotifications = new List<string>();
            foreach (KeyValuePair<string, Notification> item in cache)
            {
                if (item.Value.Interactors.Contains(notifiable))
                {
                    item.Value.Interactors.Remove(notifiable);
                    if (item.Value.Interactors.Count == 0)
                        emptyNotifications.Add(item.Key);
                }
            }
            foreach (string topic in emptyNotifications)
                cache.Remove(topic);
        }

        /// <summary>
        /// This method handles a request by a client to be notified when a
        /// subscription is made to a topic which matches its pattern.
        /// </summary>
        /// <param name="notifyMessage"></param>
        /// <param name="notifiable"></param>
        internal void HandleRequest(NotificationRequest notifyMessage, Interactor notifiable)
        {
            AppEnvironment.TraceSource.Verbose("Handling notification request for {0} on {1}", notifiable, notifyMessage);

            if (notifyMessage.IsAdd)
            {

                if (!cache.ContainsKey(notifyMessage.TopicPattern))
                    cache.Add(notifyMessage.TopicPattern, new Notification(new Regex(notifyMessage.TopicPattern)));
                if (!cache[notifyMessage.TopicPattern].Interactors.Contains(notifiable))
                {
                    cache[notifyMessage.TopicPattern].Interactors.Add(notifiable);
                    RaiseNotificationRequest(cache[notifyMessage.TopicPattern].Pattern, notifiable);
                }
            }
            else
            {
                if (cache.ContainsKey(notifyMessage.TopicPattern) && cache[notifyMessage.TopicPattern].Interactors.Contains(notifiable))
                {
                    cache[notifyMessage.TopicPattern].Interactors.Remove(notifiable);
                    if (cache[notifyMessage.TopicPattern].Interactors.Count == 0)
                        cache.Remove(notifyMessage.TopicPattern);
                }
            }
        }
        

        internal void NotifyListeners(ForwardedSubscriptionRequest message)
        {
            List<Interactor> interactors = new List<Interactor>();
            foreach (Notification notification in cache.Values)
                if (notification.Pattern.Matches(message.Topic).Count != 0)
                    foreach (Interactor notifiable in notification.Interactors)
                        interactors.Add(notifiable);

            AppEnvironment.TraceSource.Verbose("Notifying interactors[{0}] of subscription {1}", interactors.ToDelimitedString(", ", x => x.ToString()), message);

            foreach (Interactor notifiable in interactors)
                notifiable.SendMessage(message);
        }

        private void RaiseNotificationRequest(Regex topicRegex, Interactor notifiable)
        {
            if (OnNewNotification != null)
                OnNewNotification(topicRegex, notifiable);
        }
    }
}
